<!-- Licensed under a BSD license. See license.html for license -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title addtitletodoc="false">WebGL - Scaled Normals Diagram</title>
<link type="text/css" href="../../resources/webgl-tutorials.css" rel="stylesheet" />
<style>
body {
    margin: 0;
}
canvas {
    width: 100vw;
    height: 100vh;
    display: block;
}
</style>
<script id="vertexColorVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec4 color;
uniform mat4 u_worldViewProjection;
varying vec4 v_color;
void main() {
  gl_Position = u_worldViewProjection * position;
  v_color = color;
}

</script>
<script id="vertexColorFragmentShader" type="text/something-not-javascript">
precision mediump float;
uniform vec4 u_color;
varying vec4 v_color;
void main() {
  gl_FragColor = u_color * v_color;
}
</script>
<script id="normalVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec3 normal;
attribute vec4 color;
uniform mat4 u_world;
uniform mat4 u_viewProjection;
uniform mat4 u_worldInverseTranspose;
uniform float u_normalScale;
varying vec4 v_color;
void main() {
  vec3 n = normalize(mat3(u_worldInverseTranspose) * normal);
  gl_Position = u_viewProjection * (u_world * position + vec4(n * color.a * u_normalScale, 0));
  v_color = vec4(color.rgb, 1);
}

</script>
<script id="normalFragmentShader" type="text/something-not-javascript">
precision mediump float;
varying vec4 v_color;
uniform vec4 u_color;
uniform float u_light;
void main() {
  gl_FragColor = mix(u_color, v_color, u_light);
}
</script>
<script id="vertexColorFakeLightVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec3 normal;
uniform mat4 u_world;
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
varying vec3 v_normal;
void main() {
  gl_Position = u_worldViewProjection * position;
  v_normal = (u_worldInverseTranspose * vec4(normal, 0)).xyz;
}

</script>
<script id="vertexColorFakeLightFragmentShader" type="text/something-not-javascript">
precision mediump float;
uniform vec4 u_color;
uniform vec3 u_reverseLightDirection;
uniform float u_light;
varying vec3 v_normal;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              abs(l),//max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}

void main() {
  vec3 normal = normalize(v_normal);
  float light = mix(1., dot(normal, u_reverseLightDirection), u_light);
  gl_FragColor = vec4(u_color.rgb * light, u_color.a);
}
</script>
</head>
<body>
  <canvas id="canvas"></canvas>
  <pre id="sp0" style="position: absolute; text-align: center; left: 20%; bottom: 0; color: white; transform: translateX(-50%);">world</pre>
  <pre id="sp1" style="position: absolute; text-align: center; right: 20%; bottom: 0; color: white; transform: translateX(50%);">worldInverseTranspose</pre>
  <pre id="sp2" style="position: absolute; text-align: center; left: 50%; top: 0; color: white; transform: translateX(-50%);">unscaled</pre>
</body>
</html>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="../../resources/webgl-utils.js"></script>
<script src="../../resources/lessons-helper.js"></script> <!-- you can and should delete this script. it is only used on the site to help with errors -->
<script src="../../resources/twgl-full.min.js"></script>
<script>
"use strict";
var m4 = twgl.m4;
var v3 = twgl.v3;

var fPosition;
var cameraPosition;
var eyePosition;
var target;
var gridScale = 400;
var et = 1 / 30;
var mode;

// globals
var canvas;               // the canvas
var devicePixelRatio = window.devicePixelRatio || 1;

function rand(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return Math.random() * (max - min) + min;
}

function createGrid(size, subdivisions) {
  var numLines = subdivisions;
  var numVertices = numLines * 4;
  var positions = twgl.primitives.createAugmentedTypedArray(3, numVertices);
  var colors = twgl.primitives.createAugmentedTypedArray(4, numVertices);

  //  ..|..|..|..
  //  <-  size ->
  var black = [0, 0, 0, 1];
  var gray = [0.75, 0.75, 0.75, 1];

  var gridSize = size / (subdivisions + 2);
  for (var ii = 0; ii < numLines; ++ii) {
    var jj = ii - ((numLines - 1) / 2);
    var p = jj * gridSize;
    positions.push([p, 0, -size / 2]);
    positions.push([p, 0,  size / 2]);
    positions.push([-size / 2, 0, p]);
    positions.push([ size / 2, 0, p]);
    var color = jj ? gray : black;
    colors.push(color);
    colors.push(color);
    colors.push(color);
    colors.push(color);
  }

  return {
    position: positions,
    color: colors,
  };
}

function createNormalLines2(vertices) {
  var scale = 10;
  var srcPositions = vertices.position;
  var srcNormals = vertices.normal;

  const normVertices = [];

  for (var ii = 0; ii < srcPositions.length; ii += 3) {
    const cube = twgl.primitives.createCubeVertices(1);
    cube.color = new Float32Array(cube.position.length / 3 * 4);
    cube.color.forEach((v, i) => {
      const ch = i % 4;
      cube.color[i] = ch === 3
          ? 1
          : srcNormals[ii + ch] * 0.5 + 0.5;
    });
    const target = srcNormals.slice(ii, ii + 3);
    const self = [0, 0, 0];
    const up = Math.abs(target[1]) === 1 ? [1, 0, 0] : [0, 1, 0];
    const lookAt = m4.lookAt(self, target, up);
    let mat = m4.identity();
    mat = m4.translate(mat, srcPositions.slice(ii, ii + 3));
    mat = m4.multiply(mat, lookAt);
    mat = m4.scale(mat, [1.5, 1.5, scale]);
    mat = m4.translate(mat, [0, 0, -0.5]);
    twgl.primitives.reorientVertices(cube, mat);
    normVertices.push(cube);
  }

  return twgl.primitives.concatVertices(normVertices);
}
function createNormalLines(vertices) {
  var positions = [];
  var colors = [];
  var normals = [];

  var srcPositions = vertices.position;
  var srcNormals = vertices.normal;
  for (var ii = 0; ii < srcPositions.length; ii += 3) {
    positions.push(
        srcPositions[ii + 0],
        srcPositions[ii + 1],
        srcPositions[ii + 2]);
    normals.push(
        srcNormals[ii + 0],
        srcNormals[ii + 1],
        srcNormals[ii + 2]);
    colors.push(
        srcNormals[ii + 0] * 0.5 + 0.5,
        srcNormals[ii + 1] * 0.5 + 0.5,
        srcNormals[ii + 2] * 0.5 + 0.5,
        0);
    positions.push(
        srcPositions[ii    ],
        srcPositions[ii + 1],
        srcPositions[ii + 2]);
    normals.push(
        srcNormals[ii + 0],
        srcNormals[ii + 1],
        srcNormals[ii + 2]);
    colors.push(
        srcNormals[ii + 0] * 0.5 + 0.5,
        srcNormals[ii + 1] * 0.5 + 0.5,
        srcNormals[ii + 2] * 0.5 + 0.5,
        1);
  }

  return {
    position: positions,
    normal: normals,
    color: colors,
  };
}

function createApp(gl) {

  // Create Shader Programs
  var vertexColorProgramInfo = twgl.createProgramInfo(gl, [
      'vertexColorVertexShader',
      'vertexColorFragmentShader',
  ]);
  var litProgramInfo = twgl.createProgramInfo(gl, [
      'vertexColorFakeLightVertexShader',
      'vertexColorFakeLightFragmentShader',
  ]);
  var normalProgramInfo = twgl.createProgramInfo(gl, [
      'normalVertexShader',
      'normalFragmentShader',
  ]);

  var m = m4.identity();
  m4.rotateX(m, Math.PI * 0.5, m);
  m4.translate(m, [0.5, 0, -0.5], m);
  var sphereVertices = twgl.primitives.createSphereVertices(50, 24, 12);
  var sphereBufferInfo = twgl.createBufferInfoFromArrays(gl, sphereVertices);
  var sphereNormalsBufferInfo = twgl.createBufferInfoFromArrays(gl, createNormalLines(sphereVertices));
  // Create Geometry.
  var gridArrays = createGrid(36, 42);
  var gridBufferInfo = twgl.createBufferInfoFromArrays(gl, gridArrays);

  // pre-allocate a bunch of arrays
  var projection = m4.identity();
  var inverseProjection = m4.identity();
  var exampleProjection = m4.identity();
  var exampleInverseProjection = m4.identity();
  var view = m4.identity();
  var viewInverse = m4.identity();
  var world = m4.identity();
  var worldInverseTranspose = m4.identity();
  var scaleMat = m4.identity();
  var viewProjection = m4.identity();
  var inverseViewProjection = m4.identity();
  var worldViewProjection = m4.identity();
  var exampleWorldViewProjection = m4.identity();
  fPosition = new Float32Array([-50, 50, -150]);
  eyePosition = new Float32Array([150, 150, 150]);
  target = new Float32Array([0, 0, 0]);
  cameraPosition = new Float32Array([50, 200, 100]);
  var up = new Float32Array([0,1,0]);
  var v3t0 = new Float32Array(3);
  var v3t1 = new Float32Array(3);
  var v3t2 = new Float32Array(3);
  var v3t3 = new Float32Array(3);
  var m4t0 = m4.identity();
  var m4t1 = m4.identity();
  var m4t2 = m4.identity();
  var m4t3 = m4.identity();
  var zeroMat = m4.identity();
  var zero4 = new Float32Array(4);
  var one4 = new Float32Array([1,1,1,1]);
  var black = new Float32Array([0,0,0,1]);
  var red = new Float32Array([1,0,0,1]);
  var green = new Float32Array([0,1,0,1]);
  var blue = new Float32Array([0,0,1,1]);
  var ltRed = new Float32Array([1,0.5,0.5,1]);
  var ltGreen = new Float32Array([0.5,1,0.5,1]);
  var ltBlue = new Float32Array([0.5,0.5,1,1]);
  var white = new Float32Array([1,1,1,1]);
  var flashColor = new Float32Array([1,1,1,0.75]);
  var selectPoint = [0,0,0];

  // uniforms.
  var sharedUniforms = {
    u_world: world,
    u_viewProjection: viewProjection,
    u_worldViewProjection: worldViewProjection,
    u_worldInverseTranspose: worldInverseTranspose,
    u_reverseLightDirection: v3.normalize([0.25, 1, -0.37]),
    u_light: 1,
  };

  var gridUniforms = {
    u_color: [0.3,0.3,0.3,1],
  };

  var normalUniforms = {
    u_color: [0, 1, 0, 1],
    u_normalScale: 50,
  };

  var sphere0Uniforms = {
    u_color: [1, 0, 0, 1],
  };

  var sphere1Uniforms = {
    u_color: [0, 0, 1, 1],
  };

  var sphere2Uniforms = {
    u_color: [1, 0, 1, 1],
  };

  var greenUniforms = {
    u_color: [0, 1, 0, 1],
  };

  var cubeUniforms = {
    u_color: [0, 0, 1, 1],
  };

  function radToDeg(r) {
    return r * 180 / Math.PI;
  }

  function drawModel(programInfo, bufferInfo, type, uniforms, world, useWorldForWorldInverseTranspose) {
    m4.multiply(viewProjection, world, worldViewProjection);
    if (useWorldForWorldInverseTranspose) {
      m4.copy(world, worldInverseTranspose);
    } else {
      m4.inverse(world, worldInverseTranspose);
      m4.transpose(worldInverseTranspose, worldInverseTranspose);
    }
    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    twgl.setUniforms(programInfo, sharedUniforms, uniforms);
    twgl.drawBufferInfo(gl, bufferInfo, type);
  }

  function degToRad(deg) {
    return deg * Math.PI / 180;
  }

  var sp0 = document.querySelector("#sp0");
  var sp1 = document.querySelector("#sp1");
  var sp2 = document.querySelector("#sp2");

  var oldPosx;
  var mode = 0;
  function render(time) {
    var showNormals = mode < 4;
    var green = mode === 2 || mode === 3;
    var line = mode === 3;
    sharedUniforms.u_light = (mode >= 1 && mode <= 3) ? 0 : 1;


    // clear the screen.
    gl.enable(gl.DEPTH_TEST);
    gl.colorMask(true, true, true, true);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.lineWidth(window.devicePixelRatio);

    var aspect = canvas.clientWidth / canvas.clientHeight;
    var size = 700;
    var halfWidth = size * aspect;
    m4.ortho(
        -halfWidth, halfWidth, -size, size, 1, 5000,
        projection);

    var posx = canvas.clientWidth / 2 - size / halfWidth * canvas.clientWidth / 2;
    if (posx !== oldPosx) {
      oldPosx = posx;
      sp0.style.left  = (posx | 0) + "px";
      sp1.style.right = (posx | 0) + "px";
    }

    m4.lookAt(
        [0, 200, 0],
        [0, 0, 0],
        [0, 0, -1],
        viewInverse);
    m4.inverse(viewInverse, view);
    m4.multiply(projection, view, viewProjection);
    m4.inverse(viewProjection, inverseViewProjection);

    var scale = 6 + Math.sin(time * 0.0003) * 4;
    var type = line ? gl.LINES : gl.TRIANGLES;
    var ntype = gl.LINES;

    m4.translation([-size, 0, 0], world);
    m4.scale(world, [2, 2, scale], world);
    drawModel(litProgramInfo, sphereBufferInfo, type, green ? greenUniforms : showNormals ? sphere0Uniforms : sphere0Uniforms, world, true);

    if (showNormals) {
      drawModel(normalProgramInfo, sphereNormalsBufferInfo, ntype, normalUniforms, world, true);
    }

    m4.translation([ size, 0, 0], world);
    m4.scale(world, [2, 2, scale], world);
    drawModel(litProgramInfo, sphereBufferInfo, type, green ? greenUniforms : showNormals ? sphere1Uniforms : sphere0Uniforms, world);

    if (showNormals) {
      drawModel(normalProgramInfo, sphereNormalsBufferInfo, ntype, normalUniforms, world);
    }

    m4.translation([ 0, 0, 0], world);
    m4.scale(world, [2, 2, 2], world);
    drawModel(litProgramInfo, sphereBufferInfo, type, green ? greenUniforms : showNormals ? sphere2Uniforms : sphere0Uniforms, world);

    if (showNormals) {
      drawModel(normalProgramInfo, sphereNormalsBufferInfo, ntype, normalUniforms, world);
    }

    m4.translation([0, -100, 0], world);
    m4.scale(world, [100, 100, 100], world);
    drawModel(vertexColorProgramInfo, gridBufferInfo, gl.LINES, gridUniforms, world);
  }

  function toggleNormals(event) {
    event.preventDefault();
    mode = (mode + 1) % 5;
  }

  window.addEventListener('mousedown', function(event) {
      event.preventDefault();
  });
  window.addEventListener('click', toggleNormals);
  window.addEventListener('touchstart', toggleNormals);

  return {
    render: render
  };
}

var app;
function main() {
  canvas = document.querySelector("#canvas");

  var gl = twgl.getWebGLContext(canvas, {alpha: true, preMultipliedAlpha: false});
  if (!gl) {
    return;
  }

  twgl.resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  app = createApp(gl);

  var then = 0;
  function render(time) {
    app.render(time);
    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

main();
</script>




